package com.dynamicds.support.util;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ResolverUtil<T> {

	private Logger log = LoggerFactory.getLogger(getClass());	

	private Set<Class<? extends T>> matches = new HashSet<Class<? extends T>>();

	public ResolverUtil<T> find(Filter filter, String path) {
		path = getPackagePath(path);

		try {
			list(path).stream().forEach(name -> {
				if (name.endsWith(".class")) {
					addIfMatching(filter, name);
				}
			});

		} catch (Exception e) {
			e.printStackTrace();
		}
		return this;
	}

	public ResolverUtil<T> findImplementations(Class<?> parent, String... packageNames) {
		if (packageNames == null) {
			return this;
		}

		Filter filter = new ClassFilter(parent);
		for (String pkg : packageNames) {
			find(filter, pkg);
		}

		return this;
	}

	public List<String> list(String path) throws Exception {
		List<String> names = new ArrayList<String>();
		List<URL> urlList = getResources(path);
		if(urlList == null || urlList.isEmpty()) {
			URL url = new URL(path);
			names.addAll(list(url,path));
		}else {
			for (URL url : getResources(path)) {
				names.addAll(list(url, path));
			}
		}
		return names;
	}

	private List<String> list(URL url, String path) throws IOException {
		List<String> resources = new ArrayList<String>();
		// http, https, file, and jar
		String protocol = url.getProtocol();
		List<String> children = new ArrayList<String>();
		if ("file".equals(protocol)) {
			File file = new File(url.getFile());
			if (file.isDirectory()) {
				children.addAll(Arrays.asList(file.list()));
			}
		} else if ("jar".equals(protocol)) {
			JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
			if (jarURLConnection != null) {
				JarFile jarFile = jarURLConnection.getJarFile();
				if (jarFile != null) {					
					Enumeration<JarEntry> jarEntries = jarFile.entries();
					while (jarEntries.hasMoreElements()) {
						String jarEntryName = jarEntries.nextElement().getName();
						if (jarEntryName.startsWith(path)) {
							String className = jarEntryName.substring(jarEntryName.lastIndexOf("/")+1);
							if(className.equals("")) {
								continue;
							}
							children.add(className);
						}
					}
				}
			}
		}else{
			throw new RuntimeException("暂时只支持file、jar");
		}

		String prefix = url.toExternalForm();
		if (!prefix.endsWith("/")) {
			prefix += "/";
		}

		for (String child : children) {
			String resourcepath = path + "/" + child;
			resources.add(resourcepath);

			URL childurl = new URL(prefix + child);
			if("jar".equals(childurl.getProtocol())){
				if(childurl.getFile().endsWith(".class")) {
					continue;
				}
			}
			resources.addAll(list(childurl, resourcepath));
		}
		return resources;
	}

	@SuppressWarnings("unchecked")
	protected void addIfMatching(Filter filter, String fqn) {
		try {
			String externalName = fqn.substring(0, fqn.indexOf('.')).replace('/', '.');
			ClassLoader loader = getClassLoader();

			Class<?> type = loader.loadClass(externalName);
			if (filter.matches(type)) {
				matches.add((Class<T>) type);
			}
		} catch (Throwable t) {
			log.warn("Could not examine class '" + fqn + "'" + " due to a " + t.getClass().getName() + " with message: "
					+ t.getMessage());
		}
	}

	private String getPackagePath(String path) {
		return path == null ? null : path.replace(".", "/");
	}

	public List<URL> getResources(String path) throws IOException {
		return Collections.list(getClassLoader().getResources(path));
	}

	public ClassLoader getClassLoader() {
		return Thread.currentThread().getContextClassLoader();
	}

	public static interface Filter {
		boolean matches(Class<?> type);
	}

	public static class ClassFilter implements Filter {

		private Class<?> clazz;

		public ClassFilter(Class<?> type) {
			this.clazz = type;
		}

		@Override
		public boolean matches(Class<?> type) {
			return type != null && clazz.isAssignableFrom(type);
		}
	}

	public static class AnnotatedFilter implements Filter {

		private Class<? extends Annotation> annotation;

		public AnnotatedFilter(Class<? extends Annotation> annotation) {
			this.annotation = annotation;
		}

		@Override
		public boolean matches(Class<?> type) {
			return type != null && type.isAnnotationPresent(annotation);
		}
	}

	public Set<Class<? extends T>> getMatches() {
		return matches;
	}
}
